use std::{
    net::SocketAddr,
    sync::atomic::{AtomicBool, AtomicUsize, Ordering},
};

use parking_lot::RwLock;

use crate::error::*;
use crate::{
    config::backend::BackendServerConf, error::IfErr, utils::time::get_now_msec,
};

#[derive(Default)]
pub struct PeerFailedStatus {
    pub fails: usize,
    pub failed_time_ms: u64,
}

#[allow(dead_code)]
pub struct Peer {
    pub address: SocketAddr,
    pub weight: usize,
    pub max_fails: usize,
    pub failed_timeout_ms: u64,

    pub down: AtomicBool,
    pub failed_status: RwLock<PeerFailedStatus>,
    pub effective_weight: AtomicUsize,
    pub current_weight: AtomicUsize,
}

impl Default for Peer {
    fn default() -> Self {
        Peer {
            address: "0.0.0.0:0".parse().unwrap(),
            weight: 10,
            max_fails: 0,
            failed_timeout_ms: 0,

            down: AtomicBool::new(false),
            failed_status: RwLock::new(PeerFailedStatus::default()),
            effective_weight: AtomicUsize::new(0),
            current_weight: AtomicUsize::new(0),
        }
    }
}

#[allow(dead_code)]
impl Peer {
    pub fn create_by_conf(server_conf: BackendServerConf) -> Result<Self> {
        let address_port =
            format!("{}:{}", server_conf.address, server_conf.port.unwrap());

        let address: SocketAddr = address_port.parse().if_err(
            ErrorType::ParseError,
            format!("parse {} failed", server_conf.address.clone()),
        )?;
        let weight = server_conf.weight.unwrap_or_default();

        let peer = Peer {
            address,
            weight,
            max_fails: server_conf.max_fails.unwrap_or_default(),
            effective_weight: AtomicUsize::new(weight),
            ..Default::default()
        };

        Ok(peer)
    }

    pub fn check_alive(&self) -> bool {
        if self.down.load(Ordering::Relaxed) {
            return false;
        }

        if self.max_fails == 0 || self.failed_timeout_ms == 0 {
            return true;
        }

        let now_ms = get_now_msec();

        let r_status = self.failed_status.read();

        // failed_status already timedout.
        if r_status.failed_time_ms + self.failed_timeout_ms < now_ms {
            return true;
        }

        if r_status.fails < self.max_fails {
            return true;
        }

        false
    }

    pub fn feed_back(&self) {
        let now_ms = get_now_msec();

        let mut w_status = self.failed_status.write();

        if w_status.failed_time_ms + self.failed_timeout_ms < now_ms {
            w_status.failed_time_ms = now_ms;
            w_status.fails = 0;
        }

        w_status.fails += 1;
    }

    pub fn set_down(&mut self, down: bool) {
        self.down.store(down, Ordering::Relaxed);
    }
}
